//??? FPS
import std.stdio;
import std.string;
import std.random;
import std.math;

import derelict.util.exception;
import derelict.sdl.sdl;
import derelict.opengl.gl;
import derelict.opengl.glu;
import derelict.devil.il;
import derelict.devil.ilu;
import derelict.devil.ilut;

import camera;

import Digits;
import Perceptron;

// horizontal and vertical screen resolution
const int   xResolution     = 800;
const int   yResolution     = 600;

// number of bits per pixel used for display. 24 => true color
const int   bitsPerPixel    = 24;

// field of view => the angle our camera will see vertically
const float fov             = 90.f;

// distance of the near clipping plane
const float nearPlane       = .1f;

// distance of the far clipping plane
const float farPlane        = 100.f;

const float skok            = .1f;
const float angle            = 15.f;

/* Ambient Light Values */
float LightAmbient[]  = [ 5.0f,5.0f,5.0f, 1.f ];
/* Diffuse Light Values */
float LightDiffuse[]  = [ 1.0f, 1.0f, 1.0f, 1.0f ];
/* Light Position */
float LightPosition[] = [ 0.0f, 0.0f, .0f, 1.0f ];

float fogColor[] = [.5f, .5f, .5f, 1.f];
float fog_c = 12.f;

const float boxSize				= 1.f;
const float spaceBetweenBoxes	= .25f;
const float spaceBetweenPanels	= 4.f;

const int columns	= 5;
const int rows		= 6;

int[rows][columns] 	input;
int[rows][columns]	output;
int[2]				marked = [0, 0];
bool				refresh = false;

struct Sphere{
    GLUquadric    *quad;
    double        radius;
    float        x,y,z;
    float        xrot,yrot,zrot;
    float        v;
};

Camera cam;
GLuint texture[4];
Perceptron perceptrons[10];


bool missingExtensionCallback(char[] lib, char[] proc) {
	writefln("Cannot load %s :: %s", lib, proc);
    return true;
}

void LoadTexture(){
    texture[0]=ilutGLLoadImage("./tex/brick.jpg");
    texture[1]=ilutGLLoadImage("./tex/brownBrick.jpg");
    texture[2]=ilutGLLoadImage("./tex/crosshair.png");
	texture[3]=ilutGLLoadImage("./tex/smog.jpg");
}

/** Setup some basic OpenGL parameters */
void setupGL() {

    // switch to the projection mode matrix
    glMatrixMode(GL_PROJECTION);

    // load the identity matrix for projection
    glLoadIdentity();

    // setup a perspective projection matrix
    gluPerspective(fov, cast(float)xResolution / yResolution, nearPlane, farPlane);

    // switch back to the modelview transformation matrix
    glMatrixMode(GL_MODELVIEW);

    // load the identity matrix for modelview
    glLoadIdentity();
}


void init() {
//  Derelict_SetMissingProcCallback(&myMissingProcCallback);
    // initialize SDL, GL and GLU Derelict modules
    DerelictSDL.load();
    DerelictGL.load();
    DerelictGLU.load();

    // initialize SDL's VIDEO module
    SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER);

    // enable double-buffering
    SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);


    SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL);

    // create our OpenGL window
    SDL_SetVideoMode(xResolution, yResolution, bitsPerPixel, SDL_OPENGL | SDL_RESIZABLE);
    SDL_WM_SetCaption(toStringz("Zad1"), null);
	//DerelictGL.loadVersions(GLVersion.Version15);
    SDL_ShowCursor(SDL_DISABLE);
    
    LoadTexture();
    glEnable(GL_TEXTURE_2D);
    glEnable( GL_DEPTH_TEST );
	glDepthFunc(GL_LEQUAL);
//	glEnable( GL_SMOOTH );
    glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
	glClearColor(.5f, .5f, .5f, 1.f);

	glFogi(GL_FOG_MODE, GL_EXP);		// Fog Mode
	glFogfv(GL_FOG_COLOR, fogColor);	// Set Fog Color
	glFogf(GL_FOG_DENSITY, .1f);		// How Dense Will The Fog Be
	glHint(GL_FOG_HINT, GL_NICEST);		// Fog Hint Value
	glFogf(GL_FOG_START, 1.0f);			// Fog Start Depth
	glFogf(GL_FOG_END, 5.f);			// Fog End Depth
//	glFogi (GL_FOG_COORDINATE_SOURCE,GL_FOG_COORDINATE);
	glEnable(GL_FOG);					// Enables GL_FOG
	
//	void function() glFogCoordfEXT;
//	glBindExtFunc(&glFogCoordfEXT,"glFogCoordfEXT");

/+    glLightfv( GL_LIGHT1, GL_AMBIENT, LightAmbient );
    glLightfv( GL_LIGHT1, GL_DIFFUSE, LightDiffuse );
    glLightfv( GL_LIGHT1, GL_POSITION, LightPosition );
	glEnable( GL_LIGHTING );
    glEnable( GL_LIGHT1 );
+/
	cam = new Camera();
    setupGL();
	Derelict_SetMissingProcCallback(&missingExtensionCallback);

    for(int i = 0; i < rows; i++)
       	for(int j = 0; j < columns; j++){
			input[j][i] = 0;
			output[j][i] = 0;
		}
}

void perceptronsInit(){
	foreach(inout p; perceptrons)
		p = new Perceptron(rows * columns);
	foreach(i, inout p; perceptrons){
		p.train(i, 30000);
		int test = p.test(cyfry[i]);
		writefln(i,": ",test);
	}
}


// be nice and release all resources
void cleanup() {
    // tell SDL to quit
    SDL_Quit();

    // release GL, GLU and SDL's shared libs
    DerelictGLU.unload();
    DerelictGL.unload();
    DerelictSDL.unload();
    evilCleanup();
}

void move(){
	int[] scal(int[rows][columns] a){
   		int[] ret;
    	for(int i = 0; i < a[0].length; i++)
			for(int j = 0; j < a.length; j++)
       			ret ~= a[j][i];
    	return ret;
	}

	if(refresh){
		float max[] = [-1, -1];
		foreach(i, p; perceptrons){
			float wsp;
			int[30] blah = scal(input);
			if(p.detect2(blah,wsp) == 1)
				if(wsp > max[1]){
					max[0] = i;
					max[1] = wsp;
				}
		}
		if(max[0] > -1){
			int p = 0;
			for(int j = 0; j < output[0].length; j++)
				for(int k = 0; k < output.length; k++){
					output[k][j] = (cyfry[cast(int)max[0]])[p];
					p++;
				}
		}
		else{
			for(int j = 0; j < output[0].length; j++)
				for(int k = 0; k < output.length; k++)
					output[k][j] = 0;
		}
	}
	refresh = false;
}

float distance(Position camPos, float x, float y, float z){
	return sqrt((x-camPos.x)*(x-camPos.x) + (y-camPos.y)*(y-camPos.y) + (z-camPos.z)*(z-camPos.z));
}

void drawBox(){
		glBegin(GL_QUADS);							// Start Drawing Quads
//			glScalef(boxSize, boxSize, boxSize);
			// Top Face
			glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.f, -1.0f);	// Top Left Of The Texture and Quad
			glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f,  1.f,  1.0f);	// Bottom Left Of The Texture and Quad
			glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f,  1.f,  1.0f);	// Bottom Right Of The Texture and Quad
			glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.f, -1.0f);	// Top Right Of The Texture and Quad
			// Bottom Face
			glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, -1.0f, -1.0f);	// Top Right Of The Texture and Quad
			glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, -1.0f, -1.0f);	// Top Left Of The Texture and Quad
			glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f);	// Bottom Left Of The Texture and Quad
			glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f);	// Bottom Right Of The Texture and Quad
			// Front Face
			glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f);	// Bottom Left Of The Texture and Quad
			glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f);	// Bottom Right Of The Texture and Quad
			glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f,  1.0f);	// Top Right Of The Texture and Quad
			glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f,  1.0f);	// Top Left Of The Texture and Quad
			// Back Face
			glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);	// Bottom Right Of The Texture and Quad
			glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f);	// Top Right Of The Texture and Quad
			glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f);	// Top Left Of The Texture and Quad
			glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);	// Bottom Left Of The Texture and Quad
			// Right face
			glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);	// Bottom Right Of The Texture and Quad
			glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f);	// Top Right Of The Texture and Quad
			glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f,  1.0f,  1.0f);	// Top Left Of The Texture and Quad
			glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f);	// Bottom Left Of The Texture and Quad
			// Left Face
			glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);	// Bottom Left Of The Texture and Quad
			glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f);	// Bottom Right Of The Texture and Quad
			glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f,  1.0f,  1.0f);	// Top Right Of The Texture and Quad
			glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f);	// Top Left Of The Texture and Quad
		glEnd();								// Done Drawing Quads
}

void drawPanel(int[rows][columns] m, bool mark){
	glPushMatrix();
	glTranslatef(.0f, ((2.f * boxSize) + spaceBetweenBoxes) * rows + .1f, .0f);
	for(int i = 0; i < rows; i++){
		glTranslatef(.0f, -((2.f * boxSize) + spaceBetweenBoxes), .0f);
		glPushMatrix();
		for(int j = 0; j < columns; j++){
			glTranslatef((2.f * boxSize) + spaceBetweenBoxes, .0f, .0f);
			assert(m[j][i] < texture.length);
			if(marked[0]==i && marked[1] == j && mark)
				glBindTexture(GL_TEXTURE_2D, texture[3]);
			else
				glBindTexture(GL_TEXTURE_2D, texture[m[j][i]]);
			drawBox();
		}
		glPopMatrix();
	}
	glPopMatrix();
}

void drawGLFrame() {
    cam.set();

	drawPanel(input,true);
	glTranslatef(((2.f * boxSize) + spaceBetweenBoxes) * columns + spaceBetweenPanels,.0f,.0f);
	drawPanel(output,false);

    glLoadIdentity();
    glPushMatrix();
        glTranslatef(.0f,.0f,-.2f);
        glScalef(.015f,.015f,.015f);
        glEnable( GL_DEPTH_TEST );
        glBindTexture(GL_TEXTURE_2D, texture[2]);
      glEnable(GL_BLEND);
      glBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
	//glFogCoordf(.0f);
        glBegin(GL_QUADS);
            glTexCoord2f(1.f,.0f);
            glVertex3f(  1.0f,  1.0f,  1.0f ); // Top Right Of The Quad (Front)
            glTexCoord2f(.0f,.0f);
            glVertex3f( -1.0f,  1.0f,  1.0f ); // Top Left Of The Quad (Front)
            glTexCoord2f(.0f,1.f);
            glVertex3f( -1.0f, -1.0f,  1.0f ); // Bottom Left Of The Quad (Front)
            glTexCoord2f(1.f,1.f);
            glVertex3f(  1.0f, -1.0f,  1.0f ); // Bottom Right Of The Quad (Front)
        glEnd();
        glDisable(GL_BLEND);
    glPopMatrix();
}

void keyDown(SDL_KeyboardEvent e){
	if(e.keysym.sym==SDLK_ESCAPE) throw new Exception("KungFuDeathGrip");
	if(e.keysym.sym==SDLK_UP && marked[0] > 0 ) marked[0]--;
	if(e.keysym.sym==SDLK_DOWN && marked[0] < rows - 1 ) marked[0]++;
	if(e.keysym.sym==SDLK_LEFT && marked[1] >0 ) marked[1]--;
	if(e.keysym.sym==SDLK_RIGHT && marked[1] < columns - 1 ) marked[1]++;
	if(e.keysym.sym==SDLK_SPACE){
		input[marked[1]][marked[0]] = !input[marked[1]][marked[0]];
		refresh = true;
	}
    cam.keyDown(e.keysym.sym);
}

void keyUp(SDL_KeyboardEvent e){
    cam.keyUp(e.keysym.sym);
}

void mouseMotion(SDL_MouseMotionEvent e){
    if (e.x != 400 || e.y != 300) {
        cam.mouseMotion(e.x - 400, e.y - 300);
    }
}

void evilInit(){
    DerelictIL.load();
    DerelictILU.load();
    DerelictILUT.load();
    printf("IL version:%d(%d)\n",ilGetInteger(IL_VERSION_NUM),IL_VERSION);
    printf("ILU version:%d(%d)\n",iluGetInteger(ILU_VERSION_NUM),ILU_VERSION);
    printf("ILUT version:%d(%d)\n",ilutGetInteger(ILUT_VERSION_NUM),ILUT_VERSION);
    ilInit();
    iluInit();
    ilutRenderer(ILUT_OPENGL);
    ilutEnable(ILUT_OPENGL_CONV);

}

void evilCleanup(){
    DerelictIL.unload();
    DerelictILU.unload();
    DerelictILUT.unload();
}

void main() {
    uint startTime, thisTime;
    long frames;

	perceptronsInit();
    evilInit();
    init();
    scope(exit) {
        printf("\n Exiting.. \n %2.2f FPS\n",(cast(float)(frames)/(SDL_GetTicks()-startTime))*1000.0);
        cleanup();
    }      // when we exit, perform cleanup
    startTime = SDL_GetTicks();
    
    mainLoop:
    while (true) {
        SDL_Event event;

        // handle all SDL events that we might've received in this loop iteration
        while (SDL_PollEvent(&event)) {
            switch (event.type) {
                // user has clicked on the window's close button
                case SDL_QUIT:
                    break mainLoop;
                case SDL_KEYDOWN:
                    keyDown(event.key);
                    break;
                case SDL_KEYUP:
                    keyUp(event.key);
                    break;
                case SDL_MOUSEMOTION:
                    mouseMotion(event.motion);
                    break;
                // by default, we do nothing => break from the switch
                default:
                    break;
            }
       }
		SDL_WarpMouse(400,300);


        // clear the screen. by default it clears to black
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        // draw our stuff =)
        move();
        drawGLFrame();

        // swap the buffers, making our backbuffer the visible one
        SDL_GL_SwapBuffers();
        frames++;
    }
}
